package demo.game.chess.client.ui;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

import demo.game.chess.common.AbstractChessPiece;
import demo.game.chess.common.CastlingException;
import demo.game.chess.common.ChessGameManager;
import demo.game.chess.common.ChessMove;
import demo.game.chess.common.EnumChessColor;
import demo.game.chess.common.KingAttackException;
import demo.game.chess.common.MoveException;
import demo.game.chess.common.MoveHistory;
import demo.game.chess.common.CastlingException.EnumCastlingExceptionKind;
import demo.util_ui.client.ui.UIFactory;

/**
 * Grouping the set of UI components :
 * chess board, move history, warning zone. 
 * 
 * @author Prawee Sriplakich
 *
 */
public abstract class AbstractChessUI extends DockPanel {

	protected final ChessGameManager manager = new ChessGameManager();

	protected final ChessBoardUI board;

	protected final Label infoLabel = new Label();

	protected final Label warningLabel = new Label();

	protected final ScrollPanel moveHistoryScroll = new ScrollPanel();

	protected final Panel centerPanel = new VerticalPanel();

	protected final Panel rightPanel = new VerticalPanel();

	protected final FlowPanel moveHistory = new FlowPanel();

	protected final List<IChessMoveListener> moveListeners = new ArrayList<IChessMoveListener>();

	protected final CheckBox activateDnd = new CheckBox();

	protected final ListBox themeListBox = new ListBox();

	protected final Button undoButton = new Button("Undo move");

	protected final List<AbstractIconTheme> themeList = Arrays.asList(
			new ClassicIconTheme(), new ModernIconTheme());

	/**
	 * Specify the play mode : 2 human players on the same screen, 
	 * 2 human players on different screens, human VS computer.
	 * 
	 * Only the piece of the color "allowedPlayer" can be selected and moved by IHM.
	 * If null, then both white and black pieces can be moved 
	 * (2 human players on the same screen).
	 */
	protected EnumChessColor allowedPlayer = null;

	public AbstractChessUI(final EnumChessColor playerColor) {
		this.board = new ChessBoardUI(this, playerColor);

		this.moveHistory.addStyleName("chessMoveHistory");

		this.moveHistoryScroll.setWidget(this.moveHistory);
		this.moveHistoryScroll.addStyleName("chessMoveHistoryScroll");

		this.rightPanel.addStyleName("chessRightPanel");

		this.rightPanel.add(UIFactory.createLabel("Move history",
				"formGroupLabel"));
		//this.rightPanel.add(UIFactory.createLabel("", "formGroupLabelUnderline"));

		this.rightPanel.add(this.moveHistoryScroll);

		this.undoButton.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(final ClickEvent event) {
				undoLastMove();

			}
		});

		this.rightPanel.add(UIFactory.createSimplePanel(this.undoButton,
				"paddingVerticallyPanel"));

		this.rightPanel.add(UIFactory.createLabel("Preferences",
				"formGroupLabel"));
		//this.rightPanel.add(UIFactory.createLabel("", "formGroupLabelUnderline"));

		this.addPreferenceField(this.activateDnd,
				"Activate Drag and Drop (slower interaction)");

		this.activateDnd
				.addValueChangeHandler(new ValueChangeHandler<Boolean>() {

					@Override
					public void onValueChange(
							final ValueChangeEvent<Boolean> event) {

						AbstractChessUI.this.board
								.setDndSupport(AbstractChessUI.this.activateDnd
										.getValue());

					}
				});

		initIconThemeList();

		this.centerPanel.add(this.board);

		this.warningLabel.addStyleName("chessInfoLabel");
		this.warningLabel.addStyleName("chessInfoLabel_warn");

		this.infoLabel.addStyleName("chessInfoLabel");

		this.centerPanel.add(this.infoLabel);
		this.centerPanel.add(this.warningLabel);

		this.add(this.centerPanel, DockPanel.CENTER);

		this.add(this.rightPanel, DockPanel.EAST);

		updateCurrentTurn();

		setEnabledUndo(false);
	}

	public void addPreferenceField(final CheckBox input, final String label) {
		addPreferenceField(input, UIFactory
				.createLabel(label, "formFieldLabel"));

	}

	public void addPreferenceField(final String label, final Widget input) {

		addPreferenceField(UIFactory.createLabel(label, "formFieldLabel"),
				input);

	}

	public void addPreferenceField(final Widget left, final Widget right) {
		Panel p = UIFactory.createHorizontalPanel(left, right);
		p.addStyleName("formFieldElement");
		left.addStyleName("formFieldAlignRight");
		this.rightPanel.add(p);
	}

	protected void appendMoveHistory(final ChessMove move,
			final AbstractChessPiece movingPiece,
			final AbstractChessPiece eliminatedPiece) {
		String moveEntryLabel = movingPiece.getType() + ": "
				+ move.getOriginCell().getLabel() + " -> "
				+ move.getTargetCell().getLabel();
		if (null != eliminatedPiece) {
			moveEntryLabel = moveEntryLabel + " (" + eliminatedPiece.getType()
					+ " captured)";
		}

		Panel moveEntryPanel = new FlowPanel();
		moveEntryPanel.addStyleName("chessMoveEntryLine_"
				+ movingPiece.getColor());
		moveEntryPanel.add(UIFactory.createLabel(moveEntryLabel,
				"chessMoveEntry_" + movingPiece.getColor()));

		if (this.manager.getCurrentBoard().isCheckMate()) {
			moveEntryPanel.add(UIFactory.createLabel("Check Mate!",
					"chessMoveEntry_check"));
		} else if (this.manager.getCurrentBoard().isCheck()) {
			moveEntryPanel.add(UIFactory.createLabel("Check",
					"chessMoveEntry_check"));
		}

		this.moveHistory.add(moveEntryPanel);
		this.moveHistoryScroll.ensureVisible(moveEntryPanel);
	}

	protected void appendMoveHistoryUndoMessage(final MoveHistory h) {
		ChessMove move = h.getMove();

		String moveEntryLabel = "Undo move: " + move.getOriginCell().getLabel()
				+ " -> " + move.getTargetCell().getLabel();

		Panel moveEntryPanel = new FlowPanel();
		moveEntryPanel
				.addStyleName("chessMoveEntryLine_"
						+ this.manager.getCurrentBoard().getCurrentTurn()
								.getOpposite());
		moveEntryPanel.add(UIFactory.createLabel(moveEntryLabel,
				"chessMoveEntry_undo"));

		if (this.manager.getCurrentBoard().isCheckMate()) {
			moveEntryPanel.add(UIFactory.createLabel("Check Mate!",
					"chessMoveEntry_check"));
		} else if (this.manager.getCurrentBoard().isCheck()) {
			moveEntryPanel.add(UIFactory.createLabel("Check",
					"chessMoveEntry_check"));
		}

		this.moveHistory.add(moveEntryPanel);
		this.moveHistoryScroll.ensureVisible(moveEntryPanel);
	}

	public void clearInfo() {
		this.infoLabel.setText("");
	}

	public void clearWarning() {
		this.warningLabel.setText("");

	}

	public void confirmMove(final ChessMove move) {
		AbstractChessPiece movingPiece = this.manager.getCurrentBoard()
				.getPieceAt(move.getOriginCell());
		AbstractChessPiece eliminatedPiece = this.manager.getCurrentBoard()
				.getPieceAt(move.getTargetCell());

		for (IChessMoveListener l : this.moveListeners) {
			l.beforeConfirmMove(this, movingPiece, move);
		}

		this.board.confirmMove(move);

		appendMoveHistory(move, movingPiece, eliminatedPiece);
		updateCurrentTurn();

		for (IChessMoveListener l : this.moveListeners) {
			l.afterConfirmMove(this, movingPiece, move);
		}

		setEnabledUndo(true);
	}

	public EnumChessColor getAllowedPlayer() {
		return this.allowedPlayer;
	}

	public ChessBoardUI getBoard() {
		return this.board;
	}

	public ChessGameManager getManager() {

		return this.manager;
	}

	public List<IChessMoveListener> getMoveListeners() {
		return this.moveListeners;
	}

	protected void initIconThemeList() {

		this.addPreferenceField("Icon theme", this.themeListBox);

		for (AbstractIconTheme t : this.themeList) {
			this.themeListBox.addItem(t.getTitle(), t.getClass().getName());
		}

		this.themeListBox.addChangeHandler(new ChangeHandler() {

			@Override
			public void onChange(final ChangeEvent event) {
				if (AbstractChessUI.this.themeListBox.getSelectedIndex() >= 0) {
					String themeClass = AbstractChessUI.this.themeListBox
							.getValue(AbstractChessUI.this.themeListBox
									.getSelectedIndex());
					for (AbstractIconTheme t : AbstractChessUI.this.themeList) {
						if (t.getClass().getName().equals(themeClass)) {
							AbstractChessUI.this.board.setIconTheme(t);
						}
					}

				}
			}
		});
	}

	public boolean isPlayerAllowed(final EnumChessColor selectedPieceColor) {
		EnumChessColor currentTurn = this.manager.getCurrentBoard()
				.getCurrentTurn();
		return (this.allowedPlayer == null || this.allowedPlayer == currentTurn)
				&& selectedPieceColor == currentTurn;
	}

	public void setAllowedPlayer(final EnumChessColor allowedPlayer) {
		this.allowedPlayer = allowedPlayer;
	}

	public void setEnabledUndo(final boolean enabled) {
		this.undoButton.setEnabled(enabled);
	}

	public void showInfo(final String text) {
		this.infoLabel.setText(text);
	}

	public void showInfoWarning(final MoveException moveException) {
		this.warningLabel.setStyleName("chessInfoLabel_warn");
		if (moveException instanceof KingAttackException) {
			KingAttackException ex = (KingAttackException) moveException;
			this.warningLabel.setText("Danger: the attack by "
					+ ex.getKingAttackerPiece().getType() + " at "
					+ ex.getKingAttackMove().getOriginCell().getLabel());
		} else if (moveException instanceof CastlingException) {
			CastlingException ex = (CastlingException) moveException;
			String cause = "";
			if (ex.getKind() == EnumCastlingExceptionKind.king_already_moved) {
				cause = "King has already been moved";
			} else if (ex.getKind() == EnumCastlingExceptionKind.king_already_moved) {
				cause = "Rook has already been moved";
			} else if (ex.getKind() == EnumCastlingExceptionKind.king_in_check) {
				cause = "King is currently in check";
			} else if (ex.getKind() == EnumCastlingExceptionKind.prevent_by_attack) {
				cause = "The attack by " + ex.getAttackingPiece().getType()
						+ " at "
						+ ex.getPreventingAttack().getOriginCell().getLabel();
			}
			this.warningLabel.setText("Castling is prevented: " + cause);
		}
	}

	public void showInfoWarning(final String text) {

		this.warningLabel.setText(text);
	}

	public void undoLastMove() {

		for (IChessMoveListener l : this.moveListeners) {
			l.beforeUndoMove(this);
		}

		MoveHistory h = this.board.undoLastMove();

		appendMoveHistoryUndoMessage(h);
		updateCurrentTurn();

		for (IChessMoveListener l : this.moveListeners) {
			l.afterUndoMove(this);
		}

		if (null == this.manager.getCurrentBoard().getLastMove()) {

			setEnabledUndo(false);
		}

	}

	protected void updateCurrentTurn() {
		if (this.manager.getCurrentBoard().isCheckMate()) {
			showInfo(this.manager.getCurrentBoard().getCurrentTurn().getLabel()
					+ " is mated");
		} else {
			showInfo("It is now "
					+ this.manager.getCurrentBoard().getCurrentTurn()
							.getLabel() + "'s turn.");
		}
	}
}
